
/**
 * 
 * @author Jordi
 */
public class ListaGenericaFIFO<T> implements InterfaceListasIndexada<T> {

    private int contador = 0;
    private Nodo<T> primero;
    private Nodo<T> ultimo;
    private Nodo<T> indice;

    /**
     *
     * @return
     */
    public Integer elementos() {
        return contador;
    }

    /**
     * Elimina - Tiene como mision borrar un elemento de la lista que esta
     * posicionado en lugar concreto.
     * @param indice - Posicion que se quiere eliminar
     * @return T - Devuelve el elemento eliminado
     */
    public T elimina(Integer indice) {

        Nodo<T> tmp = primero;
        boolean eliminado = false;

        while (tmp != null && !eliminado) {
            if (tmp.getIndice() == indice) {
                /** enlazar los nodos anterior y siguiente al eliminar el
                 * intermedio
                 */
                if (tmp.getAnterior() != null) {
                    tmp.getAnterior().setSiguiente(tmp.getSiguiente());
                }
                if (tmp.getSiguiente() != null) {
                    tmp.getSiguiente().setAnterior(tmp.getAnterior());
                }
                // Nuevo primero
                if (tmp.getAnterior() == null) {
                    primero = tmp.getSiguiente();
                }
                // Nuevo ultimo
                if (tmp.getSiguiente() == null) {
                    ultimo = tmp.getAnterior();
                }
                tmp.setSiguiente(null);
                tmp.setAnterior(null);
                eliminado = true;
                contador--;
            } else {
                tmp = tmp.getSiguiente();
            }
        }

        return tmp == null ? null : tmp.getValor();
    }

    /**
     * estaBuida - Tiene como mision indicar si queda algun elemento
     * dentro de la lista
     * @return boleano - esta o no vacia
     */
    public boolean estaBuida() {
        return primero == null;
    }

    /**
     * indexDe- Determina la posicion en la que se encuentra un elemento
     * dentro de la lista.
     *
     * @param element - Elemento buscado
     * @return Integer - Posicion del elemento
     */
    public Integer indexDe(T element) {
        Nodo<T> tmp = primero;

        while (tmp != null) {
            if (tmp.getValor().equals(element)) {
                return tmp.getIndice();
            }
            tmp = tmp.getSiguiente();

        }
        return null;
    }

    /**
     * inserir - Inserta un elemento al final de la lista
     * @param element - Elemento que tiene que ser insertado en lista
     */
    public void inserir(T element) {
        Nodo<T> nou = new Nodo<T>(element);

        if (estaBuida()) {
            primero = nou;
            ultimo = nou;
            indice = primero;
        } else {
            ultimo.setSiguiente(nou);
            nou.setAnterior(ultimo);
            nou.setSiguiente(null);
            ultimo = nou;
        }
        contador++;
        /**Si hay más de un elemento: indice es el último + 1
         */
        if (ultimo.getAnterior() != null) {
            nou.setIndice(ultimo.getAnterior().getIndice() + 1);
        } else {
            // sino el contador
            nou.setIndice(contador);
        }
    }
    /**
     * inserir - Inserta un elemento dentro de la lista en una posicion concreta
     * @param element - Elemento a insertar
     * @param indice - Posicion en la que se desea insertar
     */
    public void inserir(T element, Integer indice) {
        // Nou element
        Nodo<T> nou = new Nodo<T>(element);

        if (estaBuida()) {
            primero = nou;
            ultimo = nou;
            this.indice = primero;
            contador++;
            nou.setIndice(contador);
        } else {
            if (insertarPorPosicion(indice, nou)) {
                contador++;
            }
        }
    }
/**
 * insertarPorPosicion - Encargado de inserta en base a un posicion
 * un elemento dentro de la lista y encargado de indicar si ha sido insertado
 * correctamente
 *
 * @param indice - Pôsicion en la que debera ir insertado el elemento
 * @param nou - Elemento que se quiere insertar
 * @return boolean - Devueelve si ha sido insertado correctamentee el elemento
 */
    private boolean insertarPorPosicion(Integer indice, Nodo<T> nou) {
        Nodo<T> tmp = primero;
        boolean inserted = false;

        nou.setIndice(indice);
        if (indice == 1) {
            nou.setAnterior(null);
            nou.setSiguiente(primero);
            primero.setAnterior(nou);
            primero = nou;
            if (this.indice == null) {
                this.indice = primero;
            }
            incrementaIndice(nou.getSiguiente());
            return true;
        }

        while (tmp != null && !inserted) {
            if (tmp.getIndice() >= indice) {
                nou.setSiguiente(tmp);
                if (tmp.getAnterior() != null) {
                    tmp.getAnterior().setSiguiente(nou);
                }

                nou.setAnterior(tmp.getAnterior());
                tmp.setAnterior(nou);

                inserted = true;
                incrementaIndice(nou.getSiguiente());
            } else {
                tmp = tmp.getSiguiente();
            }

        }
        if (!inserted) {
            nou.setAnterior(ultimo);
            ultimo.setSiguiente(nou);
            ultimo = nou;
            inserted = true;
        }
        return inserted;
    }
    /**
     * incrementaIndice - Incrementa y asigna al nuevo elemento el indice
     * @param nodo - Elemento que esta siendo insertado
     */
    private void incrementaIndice(Nodo<T> nodo) {
        int anterior = nodo.getIndice();
        while (nodo != null) {
            if (anterior == nodo.getIndice()) {
                nodo.setIndice(nodo.getIndice() + 1);
                anterior = nodo.getIndice();

            } else {
                return;
            }
            nodo = nodo.getSiguiente();
        }
    }
    /**
     * llistar - Realiza la composicon de todos los valores de los elementos
     * que saldran por pantalla
     * @return String - cadena que se devuelver
     */
    public String llistar() {
        String out = "";

        out = "\tElements: " + contador;
        Nodo<T> tmp = primero;

        while (tmp != null) {
            out += "\n\t\tValor: " + tmp.getValor() + ". Posició: " + tmp.getIndice();
            tmp = tmp.getSiguiente();
        }

        return out;
    }
    /**
     * obte - Metodo que recorre la lista en busca del elemento
     * indicado por el indicee
     * @param indice - Posicion de la cual se quiere obtener un elemento
     * @return Elemento - Devuelve el elemento de la posicion indicada
     */
    public T obte(Integer indice) {
        Nodo<T> tmp = primero;

        while (tmp != null) {
            if (tmp.getIndice() == indice) {
                return tmp.getValor();
            }
            tmp = tmp.getSiguiente();

        }
        return null;
    }
    /**
     * posa - Metodo que realiza la funcion de sustituir un elemento indicado
     * en una posicion por otro elemento
     * @param element - Elemento que se insertara
     * @param indice - Indice en que posicion se debe realizar la sustitucion
     */
    public void posa(T element, Integer indice) {
        elimina(indice);
        insertarPorPosicion(indice, ultimo);
    }

    /**
     * seguent - Avanza al siguiente nodo y devuelve el valor. si llega al final entonces
     * apunta al primero
     *
     * @return elemento
     */
    public T seguent() {
        if (indice != null) {
            indice = indice.getSiguiente();
        }
        if (indice == null) {
            indice = primero;
        }
        return (indice != null ? indice.getValor() : null);
    }

    /**
     * posicioActual- Posicion actual del indice
     *
     * @return posicion
     */
    public Integer posicioActual() {
        return (indice != null ? indice.getIndice() : null);
    }
}
